package com.huan.springcloud.spring;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 记录创建一个bean耗时多久
 * <p>
 * 如果A依赖B，则需要记录成
 * A(5s) 表示完整创建A这个Bean消耗的时间，包括子Bean的时间，以这个例子来说，其实创建A真正的时间为(5-3=2S)
 * B(3s) 表示创建B这个Bean消耗的时间
 *
 * @author huan.fu
 * @date 2022/7/16 - 09:41
 */
@Slf4j
public class BeanLoadTimeCostBeanFactory extends DefaultListableBeanFactory {

    @Getter
    @Setter
    public static class BeanCost {
        // bean的名字
        private String beanName;
        // 开始创建时间
        private long start;
        // 结束创建时间
        private long end;
        // 级别：如果理解， A 依赖B的创建，那么的级别是0，B的级别是A的级别+1,有什么用？可以知道创建A这个Bean时，依次创建它里面的Bean耗了多长时间
        private int level;
    }

    /**
     * 记录每个Bean创建的时间
     */
    public static List<BeanCost> beanCosts = new ArrayList<>(100);
    /**
     * <pre>
     * 记录上个正在创建的Bean，为什么需要
     *
     * class A {
     *      @Autowired private B b;
     *      @Autowired private C c;
     * }
     *
     * 我想达到的需求： 在打印耗时时
     * A（5s）
     *      B(2s)
     *      C(1s)
     * 即有这样的一个层级关系。
     *
     * 思路：
     * 1、将当前创建的BeanName放入到Stack中。
     * 2、当Stack不为空时，说明可能是在创建级联的Bean 级A->B。那么就需要维护B的级别是当前Stack顶元素的级别+1
     * 3、createBean方法执行完，Stack顶元素需要出栈，只有BeanCost元素的level=0时需要出栈，因为非0时已经出栈了
     * </pre>
     */
    private transient Stack<BeanCost> beanCostStack = new Stack<>();

    @Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
        long start = System.currentTimeMillis();

        BeanCost beanCost = new BeanCost();
        beanCost.setBeanName(beanName);
        beanCost.setStart(start);

        // 如果栈为空，则设置级别为0
        if (beanCostStack.isEmpty()) {
            beanCost.setLevel(0);
        } else {
            // 如果栈不为空，则设置级别为栈顶元素的级别+1
            beanCost.setLevel(beanCostStack.pop().getLevel() + 1);
        }

        beanCostStack.push(beanCost);
        Object object = super.createBean(beanName, mbd, args);
        long end = System.currentTimeMillis();

        beanCost.setEnd(end);
        beanCosts.add(beanCost);
        // 当级别是0的时候，需要将栈顶元素出栈，非0时已经出栈了。
        if (beanCost.getLevel() == 0) {
            beanCostStack.pop();
        }

        log.warn("create bean:[{}] cost:[{}ms]", beanName, end - start);
        return object;
    }
}
